--[[
 Library: SpriteAnimation: Draw animated Sprites
    written by gReY

  Class: SpriteAnimation
  Constructor: (sprite/path/url,frameWidth, frameHeight, duration)
  Member:
    Draw(x,y[,opacity]) --draws the animation
    DrawEx(rect, center, pos, opacity) --same as sprite:DrawEx()
    SetScale(xMultiplier, yMultiplier) --sets the scale of the animation
    SetAnimationRange(firstFrame, lastFrame) -- sets which and how many frames the sprite has
    GetCurrentFrameIndex()
    Start() --starts/unpauses the animation
    Stop() --pauses the animation
    Reset() --restarts the animation at the startframe
    Seek(n) --seeks a frame
    SetDuration(t) --seconds for all frames
    GetDuration()
    SetDelay(t) --seconds per frame
    GetDelay()
    SetSpeed(t) --frames per second
    GetSpeed()
    SetReverse(b) --plays animation in reverse
 ]]
--[[ Example:
function OnLoad()
    spriteAnim = SpriteAnimation("http://www.swingswingsubmarine.com/wp-content/uploads/2010/11/dude_animation_sheet.png",130,150,0.8)
    spriteAnim:SetAnimationRange(1,27)
    --spriteAnim:SetDelay(0.1)
    spriteAnim:SetSpeed(25)
    --spriteAnim:SetDuration(0.2)
    spriteAnim:SetScale(0.5,0.5)
    DelayAction(spriteAnim.Stop,1,{spriteAnim})
    DelayAction(spriteAnim.Start,3,{spriteAnim})
    DelayAction(spriteAnim.SetReverse,3,{spriteAnim,true})
    DelayAction(spriteAnim.SetReverse,10,{spriteAnim,false})
end
function OnDraw()
    --spriteAnim:Seek(5)
    spriteAnim:Draw(500,500)
end
--]]
local min, max, floor = math.min, math.max, math.floor
class("SpriteAnimation")
function SpriteAnimation:__init(sprite, frameWidth, frameHeight, duration)
    assert(sprite and frameWidth and frameHeight)
    if type(sprite)=="string" then
        if sprite:sub(1,#"http")=="http" then
            self.sprite = GetWebSprite(sprite,function(data) self.sprite = data end)
        else
            self.sprite = GetSprite(sprite)
        end
    elseif type(sprite) == "userdata" then
        self.sprite = sprite
    end
    assert(self.sprite,"SpriteAnimation: Sprite not found")
    self.frameWidth = frameWidth
    self.frameHeight = frameHeight
    self.duration = duration or 1
    self.playT = os.clock()
    self.columns = floor(self.sprite.width/self.frameWidth)
    self.rows = floor(self.sprite.height/self.frameHeight)
    self.firstFrame = 0
    self.lastFrame = self.columns*self.rows-1
end
function SpriteAnimation:Draw(x,y,opacity) --draws the animation
    self:DrawEx(
        Rect(0,0,self.frameWidth,self.frameHeight), --rect
        D3DXVECTOR3(0,0,0), --center
        D3DXVECTOR3(x,y,0), --Pos
        opacity) --opacity
end
function SpriteAnimation:DrawEx(rect, center, pos, opacity) --draws the animation with additional parameters (for cropping and such)
    if self.sprite then
        local cFrame = self:GetCurrentFrameIndex()
        local srcX, srcY = cFrame%self.columns*self.frameWidth, floor(cFrame/self.columns)*self.frameHeight
        self.sprite:DrawEx(
            Rect(srcX+max(0,rect.left),srcY+max(0,rect.top),srcX+min(self.frameWidth,rect.right),srcY+min(self.frameHeight,rect.bottom)), --rect
            center,
            pos, --Pos
            opacity or self.opacity or 0xFF) --opacity
    end
end
function SpriteAnimation:SetScale(...) --sets the scale of the animation
    if self.sprite then
        self.sprite:SetScale(...)
    end
end
function SpriteAnimation:GetCurrentFrameIndex()
    if self.stopFrame then return self.stopFrame end
    local cOff = floor(GetDrawClock(self.duration,-self.playT)*(self.lastFrame-self.firstFrame+1))
    return self.reverse and (self.firstFrame + self.lastFrame - cOff) or (self.firstFrame + cOff)
end
function SpriteAnimation:Start() --starts/unpauses the animation
    if self.stopFrame then
        self:Seek(self.stopFrame+1)
        self.stopFrame = nil
    end
end
function SpriteAnimation:Stop() --pauses the animation
    self.stopFrame = self:GetCurrentFrameIndex()
end
function SpriteAnimation:SetAnimationRange(firstFrame, lastFrame) -- sets which and how many frames the sprite has
    local cDelay = self:GetDelay()
    firstFrame = max(1,firstFrame)
    lastFrame = min(self.columns*self.rows,lastFrame)
    assert(firstFrame<=lastFrame, "First Frame must be higher number than last Frame")
    self.firstFrame = firstFrame - 1
    self.lastFrame = lastFrame - 1
    self:SetDelay(cDelay)
end
function SpriteAnimation:Reset() --restarts the animation at the startframe
    self:Seek(self.firstFrame+1)
    self:Start()
end
function SpriteAnimation:Seek(n) --seeks a frame
    if self.stopFrame then self.stopFrame = n end
    local dFrames = n-1-self.firstFrame
    self.playT = self.reverse and os.clock()-(self.lastFrame - dFrames)*self:GetDelay() or os.clock()-dFrames*self:GetDelay()
end
function SpriteAnimation:SetDuration(t) --seconds for all frames
    self.duration = t
end
function SpriteAnimation:GetDuration()
    return self.duration
end
function SpriteAnimation:SetDelay(t) --seconds per frame
    self.duration = (self.lastFrame-self.firstFrame)*t
end
function SpriteAnimation:GetDelay()
    return self.duration/(self.lastFrame-self.firstFrame)
end
function SpriteAnimation:SetSpeed(t) --frames per second
    self:SetDelay(1/t)
end
function SpriteAnimation:GetSpeed()
    return 1/self:GetDelay()
end
function SpriteAnimation:SetReverse(b) --plays animation in reverse
    local cFrame = self:GetCurrentFrameIndex()
    self.reverse = b and true or false
    self:Seek(cFrame+1)
end